/*
 * L3DGEcomms library 1.1
 *
 *
 * This work has been made possible in part by a grant from the Cisco
 * University Research Program Fund at Community Foundation Silicon Valley.
 *
 * This software is copyright (c) 2007 Swinburne University of Technology,
 * Melbourne, Australia (CRICOS number 00111D).  This software was developed by
 * Lucas Parry (lparry@groupwise.swin.edu.au) while a reseach assistant at the
 * Centre for Advanced Internet Architectures (http://caia.swin.edu.au).
 *
 * This file is used by LRCD and Greymatter.
 *
 * L3DGEComms is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * L3DGEComms is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  
 *
 * You should have received a copy of the GNU General Public License along with
 * L3DGEComms; if not, write to the Free Software Foundation, Inc., 51 Franklin St,
 * Fifth Floor, Boston, MA  02110-1301  USA
 *
 * \author Lucas Parry - CAIA, Swinburne University of Technology
 */


#include "l3dgecomms.h"
#define MSEC (100000)

struct sockaddr_in local={0},remote={0};
int L3DGESocket=0;
char  theirToken[17] = {0};
char  ourToken[17] = {0};
pthread_mutex_t theirTokenMutex;
pthread_t tokenThread;
char tokenInvalid=1;
char isAnOutputDaemon=0;
actionlist_t *actionListHead, *actionListTail;
pthread_mutex_t actionListMutex;

/* function decls */
void sendAck(int messageNumber);
void parseAction(char *msg);
void requestToken(void);
void generateToken(void);


void *receiveLoop(void *args)
{
  char msg[1024*5];
  unsigned int size;
  
  size=sizeof(remote);
  
  requestToken();
  while(1)
  {
    msg[0]=0;
    recvfrom(L3DGESocket, msg, 1024*5, 0, (struct sockaddr *)&remote, &size);
    /*printf("--------------------------------------------------\nmsg:%s\n\n",&msg[4]);*/
    if(strstr(msg,"invalidtoken"))
    {
      char* badToken;
      badToken = strstr(msg, "invalidtoken")+strlen("invalidtoken ");
      
      if (!strcmp(badToken,theirToken)) /* our token is bad, get a new one */
      {
        tokenInvalid=1;
        sleep(1);
        close(L3DGESocket);
        L3DGESocket = socket(AF_INET, SOCK_DGRAM, 0);
        bind(L3DGESocket, (struct sockaddr *)&local, sizeof(local));
        requestToken();
      }
    }
    else if(strstr(msg,"action"))
    {
      if(isAnOutputDaemon)
      {
        parseAction(msg);
      }
    }
    else if(strstr(msg,"gettoken"))
    {
      if(isAnOutputDaemon)
      {
        generateToken();
      }
    }

  }


}

void generateToken(void)
{
  int i, character;
  char msg[1024];

  unsigned int initseed = (unsigned int) time(0);
    srand(initseed);
  
  for (i=0;i<16;i++)
  {
    character = rand()%62;/* generate random number */
   
   /* this is a pretty crappy way of getting only alphanumeric chars but it'll do for now */
    if (character <= 9)
    {   
      ourToken[i]=(char)(character +'0');
    }   
    else if (character <= (9+26))
    {   
      ourToken[i]=(char)(character -10 +'a');
    }   
    else
    {   
      ourToken[i]=(char)(character -10 -26 +'A');
    }   

  }
  ourToken[16]='\0'; /* terminate string */

  
  msg[0]=msg[1]=msg[2]=msg[3]=(unsigned char)0xff;
  sprintf(&(msg[4]),"outputtoken %s %s",theirToken,ourToken); 
  printf("sending %s\n",&(msg[4])); 
  
  if (sendto(L3DGESocket, msg, strlen(msg)+1, 0, (struct sockaddr *)&remote, sizeof(remote))<0)
  {
    printf("error sending! aborting...\n");
    exit(EXIT_FAILURE);
  }


}

void setAsOutputDaemon(int i)
{
  isAnOutputDaemon=i;
}
      
void deleteActionListItem(actionlist_t *tmp)
{
  int i=0;
  for (;i<10;i++)
  {
    if (tmp->infodumpLines[i]!=NULL)
    {
      free(tmp->infodumpLines[i]);
    }
  }
  free(tmp);
}

void parseAction(char *msg)
{
  actionlist_t *temp;
  char *ptr, *split;
  int i=0;

  temp = malloc(sizeof(actionlist_t));
  memset(temp,0,sizeof(actionlist_t));

  ptr=strstr(msg,"action ~")+strlen("action ~");
  split=strchr(ptr,'~');
  if (split == 0)
  {
    //debug_printf("invalid action receieved, ignoring...\n");
    deleteActionListItem(temp);
    return;
  }
  *(split++)=0;

  /*check token */
  if (strcmp(ptr,ourToken))
  {
    //debug_printf("Non-matching token!\n");
    deleteActionListItem(temp);
    generateToken();
    return;
  }

  ptr=split;
  split=strchr(ptr,'~');
  if (split == 0)
  {
    //debug_printf("invalid action receieved, ignoring...\n");
    deleteActionListItem(temp);
    return;
  }
  *(split++)=0;
  
  temp->messageNumber=atoi(ptr);
  /* check if we've recieved this already */
  pthread_mutex_lock(&(actionListMutex));
  {
    int existing=0;
    actionlist_t *listPtr;
    
    listPtr=actionListHead;


    while (listPtr!=NULL)
    {
      if (listPtr->messageNumber ==temp->messageNumber)
      {
        /* match found */
        existing=1;
        break;
      }
      listPtr=listPtr->next;
    }

    if (existing)
    {
      //debug_printf("already recieved, resend ack...\n");
      sendAck(temp->messageNumber);
      pthread_mutex_unlock(&(actionListMutex));
      deleteActionListItem(temp);
      return;

    }

  }

  ptr=split;
  split=strchr(ptr,'~');
  if (split == 0)
  {
    //debug_printf("invalid action receieved, ignoring...\n");
    pthread_mutex_unlock(&(actionListMutex));
    deleteActionListItem(temp);
    return;
  }
  *(split++)=0;
  temp->hostId=atoi(ptr);

  ptr=split;
  split=strchr(ptr,'~');
  if (split == 0)
  {
    //debug_printf("invalid action receieved, ignoring...\n");
    pthread_mutex_unlock(&(actionListMutex));
    deleteActionListItem(temp);
    return;
  }
  *(split++)=0;
  temp->weaponNumber=atoi(ptr);

  ptr=split;
  split=strchr(ptr,'~');
  if (split == 0)
  {
    //debug_printf("invalid action receieved, ignoring...\n");
    pthread_mutex_unlock(&(actionListMutex));
    deleteActionListItem(temp);
    return;
  }
  *(split)=0;

  while (((split=strchr(ptr,'\n'))!=0)&&(i<10))
  {
    *(split++)=0;
    temp->infodumpLines[i]=malloc(sizeof(char)*(strlen(ptr)+1));
    strcpy(temp->infodumpLines[i],ptr);
    i++;
    ptr=split;
  }


  /*printf("msg:%i host:%i weap:%i\n",temp->messageNumber,temp->hostId,temp->weaponNumber);

  i=0;
  while(temp->infodumpLines[i]!=0)
  {
    printf("line%i: %s\n",i,temp->infodumpLines[i]);
    i++;
  }*/

  
  if (actionListHead==NULL)
  {
    actionListHead=actionListTail=temp;

  }
  else
  {
    actionListTail->next=temp;
    actionListTail=temp;

  }
  
  sendAck(temp->messageNumber);

  pthread_mutex_unlock(&(actionListMutex));
  

}

void requestToken(void)
{
  while (tokenInvalid!=0)
  {
    char msg[1024];
    char *split;
    unsigned int size;
  
    struct timeval tv={0};
    


    /* 0xffffffff lets q3a know that we're sending connectionless packets */
    msg[0]=msg[1]=msg[2]=msg[3]=(unsigned char)0xff;

    strcpy(&(msg[4]),"gettoken"); 

    //debug_printf("sent message of length %04i: %s\n", (int)strlen(msg),msg);
    if (sendto(L3DGESocket, msg, strlen(msg)+1, 0, (struct sockaddr *)&remote, sizeof(remote))<0)
    {
      printf("error sending! aborting...\n");
      exit(EXIT_FAILURE);
    }

    {
      int i;
      for (i=0;i<1024;i++)
      {
        msg[i]='\0';
      }
    }
    size=sizeof(remote);
    
    /* set sockect timeout */
    tv.tv_sec=5;
    setsockopt(L3DGESocket,SOL_SOCKET,SO_RCVTIMEO, &tv, sizeof(tv));

    recvfrom(L3DGESocket, msg, 1024, 0, (struct sockaddr *)&remote, &size);
    
    /* turn off socket timeout */
    tv.tv_sec=0;
    setsockopt(L3DGESocket,SOL_SOCKET,SO_RCVTIMEO, &tv, sizeof(tv));
    
    split= memchr(&msg[4],(int)':',1024);
    if (split== NULL)
    {
      printf("error parsing token, retrying...\n");

      continue;
    }
    split++;
    strcpy(theirToken,split);
    //debug_printf("token received: %s\n",theirToken);
    tokenInvalid=0;
  }
}
/* function decl (used only in this file) */
int lookup_host(struct sockaddr_in *ra, char *hn, unsigned short rp);

void safeStrings(char ** string)
{
  char* position;

  while ((position=strchr(*string,'\n'))!=0)
  {
    *position='@';
  }

}
/**
 * sendMessage  sends out the given data to the l3dgeworld server in the
 * correct syntax over a udp port
 * \param hostid is the id of the host who this data relates to. 1-64 for
 * the L3DGEWorld 2.0 map.
 * \param metric is the metric to update.
 * \param field is the field of the metric to update.
 * \param value is a string containing the data we wish to set.
 */
void sendMessage(int hostid, int metric, char *field, char *value)
{
  char msg[1024];
  /*   char pps[32]; */
  char delimiter[2];
  const struct timespec sleeptime={0,10*MSEC};
 
  while(tokenInvalid)
  {
    ;
  } 

  /*  define our delimiter */
  delimiter[0]=126; /*  tilde ~ */ 
  delimiter[1]='\0';

  safeStrings(&value);

  /*  0xffffffff lets q3a know that we're sending connectionless packets */
  msg[0]=msg[1]=msg[2]=msg[3]=(unsigned char)0xff;

  /*  specify l3dge messaging protocol, command cvarset */
  sprintf(&msg[4],"l3dge %s %s%03i%s%d%s%s%s%s%s",
      theirToken,delimiter,
      hostid,delimiter,
      metric,delimiter,
      field,delimiter,
      value,delimiter);

  //debug_printf("sent message of length %04i: %s\n", (int)strlen(msg),msg);
  if (sendto(L3DGESocket, msg, strlen(msg)+1, 0, (struct sockaddr *)&remote, sizeof(remote))<0)
  {
    printf("error sending! aborting...\n");
    exit(EXIT_FAILURE);
  }
  nanosleep(&sleeptime,NULL);
}



/**
 * setupComms sets up the udp socket we need to send data to the l3dgeworld server when using a non-standard port
 * \param ipAddress is the ip address of the l3dgeserver as a string in.
 * standard dotted notation.
 * \param port is the port the l3dgeserver is listening on. 
 */
void setupComms(char *local_ip,int local_port,char *remote_ip,int remote_port)
{
  int rc;
  //debug_printf("setting up udp port...\n");

  pthread_mutex_init(&(actionListMutex),NULL);
  pthread_mutex_init(&(theirTokenMutex),NULL);

  L3DGESocket = socket(AF_INET, SOCK_DGRAM, 0);
  
  if(!lookup_host(&local, local_ip, local_port))
  {
    exit(EXIT_FAILURE);
  }

  bind(L3DGESocket, (struct sockaddr *)&local, sizeof(local));

  if(!lookup_host(&remote, remote_ip, remote_port))
  {
    exit(EXIT_FAILURE);
  }
  
  if ((rc=pthread_create(&tokenThread,NULL,receiveLoop,(void *) NULL))) /*  start thread to regularly print our output */
  {
    printf("Thread creation failed: %d\n",rc);
    printf("exiting...\n");
    exit(1);
  }
}

/*
 * resolve a hostname to a sockaddr_in struct.
 * we first try treating it like an ip address in a.b.c.d notation
 * then, if that fails, we try to resolve using DNS ways
 * if all fails, we return 0. (failed)
 * if we get the sockaddr_in struct all filled out, we return 1.
 * from : http://attrition.org/tools/other/binfo.c
 */
int lookup_host( struct sockaddr_in *address, char *hn, unsigned short port)
{
   struct hostent *he;
   
   if (hn!=NULL)
   {
     //debug_printf("lookup up host: %s:%i\n",hn,port);
   }
   else
   {
     //debug_printf("lookup up host: ANY:%i\n",port);
   }

   address->sin_family = AF_INET;
   if (port==0)
   {
     address->sin_port = INADDR_ANY;
   }
   else
   {
     address->sin_port = htons(port);
   }
   if (hn==NULL)
   {
     address->sin_addr.s_addr = INADDR_ANY;
     return 1;
   }

   if ((address->sin_addr.s_addr = inet_addr(hn)) != -1)
   {
     return 1;
   }
   if ((he = gethostbyname(hn)) != (struct hostent *)NULL)
   {
     memcpy(&address->sin_addr.s_addr, he->h_addr, 4);
     return 1;
   }
   herror("Unable to resolve hostname\n");
   return 0;
}

void sendHostRepositionTrigger(int hosts[], int arraySize)
{
  char msg[1024];
  char *hostList;
  int i;

  hostList=malloc(sizeof(char)*5*arraySize);
  strcpy(hostList,"~");

  /*  0xffffffff lets q3a know that we're sending connectionless packets */
  msg[0]=msg[1]=msg[2]=msg[3]=(unsigned char)0xff;
  
  for (i=0;i<arraySize;i++)
  {
    char tempInt[32]={0};

    sprintf(tempInt,"%i",hosts[i]);
    
    hostList = strcat(hostList,tempInt);
    hostList = strcat(hostList,"~");

  }

  while(tokenInvalid)
  {
    ;
  } 
  sprintf(&msg[4]," move %s %s",theirToken,hostList);
  free(hostList);


  //debug_printf("sent message of length %04i: %s\n", (int)strlen(msg),msg);
  if (sendto(L3DGESocket, msg, strlen(msg)+1, 0, (struct sockaddr *)&remote, sizeof(remote))<0)
  {
    printf("error sending! aborting...\n");
    exit(EXIT_FAILURE);
  }



}

void sendFeedback(char *message)
{
  char msg[1024];
  if (strlen(message)>1019)
  {
    message[1019]=0;
  }

  /*  0xffffffff lets q3a know that we're sending connectionless packets */
  msg[0]=msg[1]=msg[2]=msg[3]=(unsigned char)0xff;

  sprintf(&msg[4],"feedback %s",message);

  while(tokenInvalid)
  {
    ;
  } 
  //debug_printf("sent message of length %04i: %s\n", (int)strlen(msg),msg);
  if (sendto(L3DGESocket, msg, strlen(msg)+1, 0, (struct sockaddr *)&remote, sizeof(remote))<0)
  {
    printf("error sending! aborting...\n");
    exit(EXIT_FAILURE);
  }


}

void sendAck(int messageNumber)
{
  char msg[1024];

  /*  0xffffffff lets q3a know that we're sending connectionless packets */
  msg[0]=msg[1]=msg[2]=msg[3]=(unsigned char)0xff;

  /* specify l3dge messaging protocol, command cvarset */
  sprintf(&msg[4],"ack %s %i",theirToken,messageNumber);

  //debug_printf("sent message of length %04i: %s\n", (int)strlen(msg),msg);
  if (sendto(L3DGESocket, msg, strlen(msg)+1, 0, (struct sockaddr *)&remote, sizeof(remote))<0)
  {
    printf("error sending! aborting...\n");
    exit(EXIT_FAILURE);
  }


}

/**
 * closeComms shuts down the udp socket
 */
void closeComms(void)
{
  //debug_printf("tearing down udp port\n");
  close(L3DGESocket);
}
